package com.synthefact.daj.view.awt;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Panel;
import java.awt.Point;
import java.awt.Scrollbar;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;

import com.synthefact.daj.view.VisualConfiguration;

/**
 * A Panel for the visualizer. A screen has a canvas for the node and channel
 * visuals.
 * <p>
 * Copyright (C) 2011 Synthefact
 * <p>
 * Based on code from (c) 1997, Wolfgang Schreiner <Wolfgang.Schreiner@risc.uni-linz.ac.at>
 * http://www.risc.uni-linz.ac.at/software/daj
 */
public class Screen extends Panel implements AdjustmentListener, MouseMotionListener, MouseListener {

    /** The frame for the panel */
    private Visualizer iVisualizer;

    /** the canvas */
    private Panel iCanvas;

    /** the scrollbars */
    private Scrollbar iHbar;
    private Scrollbar iVbar;

    /** logical size of screen */
    private int iXsize;
    private int iYsize;

    /** x and y offset */
    private int iXoffset;
    private int iYoffset;

    /** informational window for visual elements */
    private Popup iWindow;

    /** true iff window is currently shown */
    private boolean iWindowShown;

    /** (the visual representation of) the currently selected the node */
    private NodeVisual currentNode;

    /** the currently selected channel */
    private ChannelVisual currentChannel;

    /** set to currently selected node for movement */
    private NodeVisual selectedNode;

    /** all the node visuals on the canvas */
    private List<NodeVisual> nodeVisualList = new ArrayList<NodeVisual>();

    /** all the channel visuals on the canvas */
    private List<ChannelVisual> channelVisualList = new ArrayList<ChannelVisual>();

    // TODO - is time part of the model (rather than the view?
    /** current network time */
    private int time = 0;

    /* current number of messages */
    public int iLastNumMsgs, iLastNumBaseMsgs = 0;

    /**
     * Construct a new Screen at position (x,y).
     * 
     * @param v
     *            the visualizer for this screen.
     * @param x
     *            the x coordinate
     * @param y
     */
    public Screen(Visualizer v, int x, int y) {
	// set visualizer
	iVisualizer = v;

	// create components
	iCanvas = new Panel();
	iHbar = new Scrollbar(Scrollbar.HORIZONTAL);
	iVbar = new Scrollbar(Scrollbar.VERTICAL);

	// set layout and add components
	setLayout(new BorderLayout(0, 0));
	add("Center", iCanvas);
	add("South", iHbar);
	add("West", iVbar);

	// set background color of canvas
	iCanvas.setBackground(Color.white);

	// set size of screen
	iXsize = x;
	iYsize = y;

	// set coordinates and size of canvas
	iXoffset = 0;
	iYoffset = 0;

	// create window for screen information
	newWindow();
	// register event handlers
	iHbar.addAdjustmentListener(this);
	iVbar.addAdjustmentListener(this);
	iCanvas.addMouseMotionListener(this);
	iCanvas.addMouseListener(this);
    }

    /**
     * Instanciate a new new pop-up window.
     */
    public void newWindow() {
	iWindow = new Popup(iVisualizer);
	iWindowShown = false;
    }

    /**
     * Return preferred size for layout manager.
     */
    public Dimension getPreferredSize() {
	// results are initially 0, thus not useable
	return new Dimension(iXsize, // +vbar.getSize().width,
		iYsize); // +hbar.getSize().height);
    }

    /**
     * Return minimum size for layout manager.
     */
    public Dimension getMinimumSize() {
	// results are initially 0, thus not useable
	return new Dimension(iXsize, // +vbar.getSize().width,
		iYsize); // +hbar.getSize().height);
    }

    /**
     * add node `n` to screen
     * 
     * @param n
     *            the node visual to add.
     */
    public void add(NodeVisual n) {
	nodeVisualList.add(n);
    }

    /**
     * Add channelVisual `c` to screen.
     * 
     * @param c
     *            the channel visual to add.
     */
    public void add(ChannelVisual c) {
	channelVisualList.add(c);
    }

    /**
     * Return graphics to paint on.
     */
    public Graphics getGraphics() {
	return iCanvas.getGraphics();
    }

    /**
     * Called when window size is changed, update scrollbars accordingly
     */
    public synchronized void setBounds(int x, int y, int width, int height) {
	// avoid warnings, can be ignored
	if (width == 0 && height == 0)
	    return;
	// reset offset such that drawing is not clipped on resize
	iXoffset = 0;
	iYoffset = 0;
	// update screen size, (cannot adjust to scrollbar, see
	// getPreferredSize)
	int xlen = width; // -vbar.getSize().width;
	int ylen = height; // -hbar.getSize().height;
	// update scrollbar values
	iHbar.setValues(0, xlen, 0, iXsize);
	iVbar.setValues(0, ylen, 0, iYsize);
	iHbar.setBlockIncrement(xlen);
	iVbar.setBlockIncrement(ylen);
	// reshape window
	super.setBounds(x, y, width, height);
    }

    /**
     * Close popup window.
     */
    public void close() {
	if (iWindowShown) {
	    iWindow.setVisible(false);
	    redraw();
	    iWindowShown = false;
	}
    }

    /**
     * Draws the number of non-algorithm messages.
     */
    public void drawNumberOfBaseMessages(int numMsgs) {
	Graphics g = getGraphics();
	Font font = VisualConfiguration.getSmallFont();
	g.setFont(font);
	g.setColor(Color.white);
	int height = getFontMetrics(font).getHeight();
	g.drawString("#BaseMsg: " + String.valueOf(iLastNumBaseMsgs), 1, height * 2);
	iLastNumBaseMsgs = numMsgs;
	g.setColor(Color.black);
	g.drawString("#BaseMsg: " + String.valueOf(numMsgs), 1, height * 2);
    }

    /**
     * Draws the number of algorithm messages.
     */
    public void drawNumberOfAlgorithmMessages(int numMsgs) {
	Graphics g = getGraphics();
	Font font = VisualConfiguration.getSmallFont();
	g.setFont(font);
	g.setColor(Color.white);
	int height = getFontMetrics(font).getHeight();
	g.drawString("#AlgMsg: " + String.valueOf(iLastNumMsgs), 1, height * 3);
	iLastNumMsgs = numMsgs;
	g.setColor(Color.black);
	g.drawString("#AlgMsg: " + String.valueOf(numMsgs), 1, height * 3);
    }

    /**
     * 
     */
    @Override
    public void mouseClicked(MouseEvent e) { // do nothing
    }

    /**
     * 
     */
    @Override
    public void mousePressed(MouseEvent e) {
	selectNode(e.getX(), e.getY());
    }

    /**
     * 
     */
    @Override
    public void mouseReleased(MouseEvent e) {
	selectedNode = null;
    }

    /**
     * 
     */
    @Override
    public void mouseEntered(MouseEvent e) { // do nothing
    }

    /**
     * 
     */
    @Override
    public void mouseExited(MouseEvent e) { // do nothing
    }

    /**
     * 
     */
    @Override
    public void mouseDragged(MouseEvent e) {
	if (selectedNode == null) {
	    selectNode(e.getX(), e.getY());
	} else {
	    moveNode(e.getX(), e.getY());
	}
    }

    /**
     * 
     */
    @Override
    public void mouseMoved(MouseEvent e) {
	int x = e.getX();
	int y = e.getY();
	focus(x, y);
	redraw();
    }

    /**
     * 
     */
    @Override
    public void adjustmentValueChanged(AdjustmentEvent e) {
	int offset = e.getValue();
	Object target = e.getSource();
	if (target == iHbar)
	    iXoffset = offset;
	else {
	    iYoffset = offset;
	}
	clear();
	redraw();
    }

    /**
     * 
     */
    public void paint(Graphics g) {
	drawTime(time);
	for (ChannelVisual cv : channelVisualList) {
	    cv.draw();
	}
	for (NodeVisual nv : nodeVisualList) {
	    nv.draw();
	}
    }

    /**
     * 
     * @param x
     * @param y
     */
    private void selectNode(int x, int y) {
	// logical coordinates of focus
	int xpos = x + iXoffset;
	int ypos = y + iYoffset;
	// select node for movement
	selectedNode = null;
	for (NodeVisual nv : nodeVisualList) {
	    if (nv.inside(xpos, ypos)) {
		selectedNode = nv;
		return;
	    }
	}
    }

    /**
     * Draw the network time on the screen.
     * 
     * @param t
     *            the network time as an integer.
     */
    private void drawTime(int t) {
	Graphics g = getGraphics();
	Font font = VisualConfiguration.getSmallFont();
	g.setFont(font);
	g.setColor(Color.white);
	int height = getFontMetrics(font).getHeight();
	g.drawString("Global Time: " + String.valueOf(time), 1, height);
	g.setColor(Color.black);
	g.drawString("Global Time: " + String.valueOf(t), 1, height);
	time = t;
    }

    /**
     * Move selected node to canvas coordinates `x` and `y` and redraw screen
     * 
     * @param x
     *            the new x coordinate for the node.
     * @param y
     *            the new y coordinate for the node.
     */
    private void moveNode(int x, int y) {

	// logical coordinates of focus
	int xpos = x + iXoffset;
	int ypos = y + iYoffset;
	// update position and redraw
	selectedNode.setPosition(xpos, ypos);
	clear();
	redraw();
    }

    /**
     * clears the screen
     */
    public void clear() {
	getGraphics().clearRect(0, 0, iXsize, iYsize);
    }

    // --------------------------------------------------------------------------
    // determine mouse focus on canvas coordinates `x` and `y`
    // --------------------------------------------------------------------------
    private void focus(int x, int y) {
	// base coordinates of screen
	Point cloc = iCanvas.getLocationOnScreen();
	int xbase = cloc.x;
	int ybase = cloc.y;
	// logical coordinates of focus
	int xpos = x + iXoffset;
	int ypos = y + iYoffset;
	// try to display node status
	int nodeNum = nodeVisualList.size();
	for (NodeVisual nodeVisual : nodeVisualList) {

	    if (nodeVisual.inside(xpos, ypos)) {
		if (!iWindowShown || currentNode != nodeVisual) {
		    if (iWindowShown) {
			iWindow.setVisible(false);
			redraw();
			iWindowShown = false;
		    }
		    nodeVisual.write();
		    int nodeRadius = VisualConfiguration.getNodeRadius();
		    popup(xbase - iXoffset + nodeVisual.getX() + nodeRadius, ybase - iYoffset + nodeVisual.getY() + nodeRadius,
			    nodeVisual.getText());
		    currentNode = nodeVisual;
		    currentChannel = null;
		}
		return;
	    }
	}
	// try to display channel contents based on channel bullet
	for (ChannelVisual channelVisual : channelVisualList) {
	    if (channelVisual.insideBullet(xpos, ypos)) {
		if (!iWindowShown || currentChannel != channelVisual) {
		    if (iWindowShown) {
			iWindow.setVisible(false);
			redraw();
			iWindowShown = false;
		    }
		    // channelVisual.write();
		    // int chRadius = visualizer.getApplication().channelRadius;
		    popup(xbase - iXoffset + channelVisual.getXPosTube(), ybase - iYoffset + channelVisual.getYPosTube(),
			    "channel test goes here.");
		    currentChannel = channelVisual;
		    currentNode = null;
		}
		return;
	    }
	}
	// try to display channel contents based on channel tube
	for (ChannelVisual channelVisual : channelVisualList) {

	    if (channelVisual.insideTube(xpos, ypos)) {
		if (!iWindowShown || currentChannel != channelVisual) {
		    if (iWindowShown) {
			iWindow.setVisible(false);
			redraw();
			iWindowShown = false;
		    }
		    // channelVisual.write();
		    popup(xbase - iXoffset + channelVisual.getXPosTube(), ybase - iYoffset + channelVisual.getYPosTube(),
			    "channel text goes here");
		    currentChannel = channelVisual;
		    currentNode = null;
		}
		return;
	    }
	}
	// no status to be displayed
	if (iWindowShown) {
	    iWindow.setVisible(false);
	    redraw();
	    iWindowShown = false;
	    currentNode = null;
	    currentChannel = null;

	}
    }

    /**
     * write `text` separated by newlines into window positioned at `xpos/ypos`
     * 
     * @param xpos
     * @param ypos
     * @param text
     */
    private void popup(int xpos, int ypos, String text) {

	iWindow.popup(xpos, ypos, text);
	// window is shown now
	iWindowShown = true;
    }

    /**
     * redraw the panel
     */
    public void redraw() {
	paint(getGraphics());
    }

    /**
     * return offset
     * 
     * @return
     */
    public int getXOffset() {
	return iXoffset;
    }

    /**
     * return offset
     * 
     * @return
     */
    public int getYOffset() {
	return iYoffset;
    }

}
